﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ComputerGraphics.Geometry;
using ComputerGraphics.Graphics;
using ComputerGraphics.Utils;

namespace ComputerGraphics.Render
{
    public class RecursiveRayTracer : Renderer
    {
        private static int maxRecurCnt = 5;

        private const double c1 = 0.25;
        private const double c2 = 0.1;
        private const double c3 = 0.1;

        private const double n = 8;

        public RecursiveRayTracer(IntPair canvasSize)
            : base(canvasSize)
        {
        }

        public override ColorTriple[][] Render(Scene scene, Camera camera) {
            ColorTriple[][] ret = new ColorTriple[canvasSize.X][];
            this.scene = scene;

            for (int x = 0; x < canvasSize.X; x++)
            {
                ret[x] = new ColorTriple[canvasSize.Y];
                for (int y = 0; y < canvasSize.Y; y++)
                {
                    //if (x == 150 && y == 150)
                    //    Console.WriteLine("Test");

                    Ray ray = camera.GetRay((double)x / canvasSize.X, 1 - (double)y / canvasSize.Y);

                    ret[x][y] = Trace(ray, 0);
                }
            }
            return ret;
        }

        protected ColorTriple Trace(Ray ray, int recurCnt)
        {
            ColorTriple ret;

            double minDist = Double.MaxValue;
            Intersection nearest = null;
            foreach (Shape s in scene.Shapes)
            {
                Intersection its = s.Intersect(ray);
                if (its != null && its.Distance < minDist)
                {
                    minDist = its.Distance;
                    nearest = its;
                }
            }

            if (nearest == null)
                ret = scene.BackGroundColor;
            else
                ret = GetShade(nearest, false, recurCnt);

            return ret;
        }

        protected ColorTriple TraceInternal(Ray ray, Shape shape, int recurCnt)
        {
            ColorTriple ret;
            Intersection its = shape.InternalIntersect(ray);

            if (its == null)
                ret = scene.BackGroundColor;
            else
                ret = GetShade(its, true, recurCnt);

            return ret;
        }

        protected ColorTriple GetShade(Intersection its, bool isInternal, int recurCnt)
        {
            ColorTriple ret = 0;
            Shape shape = its.Shape;
            Ray ray = its.Ray;
            Texture texture = shape.Mapping;
            ret = ret + scene.AmbientLight.Color * texture.AmbientReflectionCoefficient * texture.DiffuseColor;

            foreach (PointLight sl in scene.PointLights)
            {
                Point3D incomingLight = sl.Position - its.Position;
                double dist = incomingLight.Norm;
                Point3D ilDirection = incomingLight.Direction;

                if (MathUtils.GetZeroDouble(its.Normal * ilDirection) <= 0) continue;

                double scale = 1;

                if (isInternal)
                    scale = its.Shape.Mapping.TransmissionCoefficient;

                foreach (Shape s in scene.Shapes)
                {
                    if (isInternal && s == its.Shape) continue;

                    Intersection newIts = s.Intersect(new Ray(sl.Position, -ilDirection));

                    if (newIts == null) continue;

                    if (MathUtils.GetZeroDouble(newIts.Distance - dist) < 0)
                        scale *= s.Mapping.TransmissionCoefficient * s.Mapping.TransmissionCoefficient;
                }

                double d2 = incomingLight.SquareNorm;
                double d = incomingLight.Norm;
                double fatt = Math.Min(1.0 / (c1 + c2 * d + c3 * d2), 1);

                ColorTriple dpart = texture.DiffuseColor * texture.DiffuseReflectionCoefficient * (its.Normal * ilDirection);

                Point3D r = GeometryUtils.CalculateSymmetric(ilDirection, its.Normal);
                Point3D h = (ilDirection - ray.Direction).Direction;

                ColorTriple ones = 1;
                ColorTriple spart = ones * (texture.SpecularCoefficient * Math.Pow(h * its.Normal, n));

                ret = ret + (dpart + spart) * (fatt * scale) * sl.Color;
            }

            if (recurCnt < maxRecurCnt)
            {
                if (texture.SpecularCoefficient > 0)
                {
                    Point3D newDir = its.Normal * (-2 * its.Normal * ray.Direction) + ray.Direction;
                    Ray reclectRay = new Ray(its.Position, newDir);

                    ColorTriple rcolor = Trace(reclectRay, recurCnt + 1);
                    ret = ret + rcolor * texture.SpecularCoefficient;
                }

                if (texture.TransmissionCoefficient > 0)
                {
                    Ray transRay;
                    ColorTriple tcolor = 0;

                    Point3D i = -ray.Direction;
                    Point3D n = its.Normal;
                    Point3D t = GeometryUtils.CalculateRefraction(i, n, texture.Eta);

                    if (t * n < 0)
                    {
                        transRay = new Ray(its.Position, t);
                        if (!isInternal)
                            tcolor = TraceInternal(transRay, shape, recurCnt + 1);
                        else
                            tcolor = Trace(transRay, recurCnt + 1);
                    }

                    ret = (1 - texture.TransmissionCoefficient) * ret + tcolor * texture.TransmissionCoefficient;
                }
            }

            return ret;
        }
     }
}
